// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging.EventFormatters {

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml;

using NSpring.Logging.Common;
using NSpring.Logging.DataFormatters;
using NSpring.Logging.XML;

/// <summary>
/// <para>
/// Renders Event objects to XML.  This is the default formatter type used by
/// MessageQueueLogger, StreamLogger, and many other logging classes.
/// </para>
/// <para>
/// In an effort to make the default formatting as easy to integrate with existing 
/// XML schemas as possible, output has been made configurable in several ways. 
/// For instance, the casing and word separators in both element and attribute names
/// can be changed, as well as many of the element and attribute names themselves (see the 
/// XMLFormatOptions property and the many *Name properties).  For this reason, 
/// it's impossible to provide a schema for all possible XML representations.
/// </para>
/// <para>
/// An Event object containing one or more System.Xml.XmlNode objects in its Data property will have the XML
/// passed through unescaped.  This is intended to allow easier integration with existing applications.
/// </para>
/// <para>
/// Sample output:
/// </para>
/// <code>
/// &lt;event time=&quot;2003-10-07T02:30:51.795-04:00&quot; level=&quot;info&quot; category=&quot;test&quot; code=&quot;500&quot;&gt;
///    &lt;context&gt;
///       &lt;system name=&quot;LIQUIDBLUE&quot;/&gt;
///       &lt;user name=&quot;jvarszegi&quot;/&gt;
///       &lt;application name=&quot;NSpring&quot;/&gt;
///       &lt;thread priority=&quot;normal&quot;/&gt;
///       &lt;properties/&gt;
///    &lt;/context&gt;
///    &lt;text&gt;Hello, world!&lt;/text&gt;
///    &lt;data type=&quot;list&quot;&gt;
///       &lt;value type=&quot;string&quot;&gt;one&lt;/value&gt;
///       &lt;value type=&quot;string&quot;&gt;two&lt;/value&gt;
///       &lt;value type=&quot;string&quot;&gt;three&lt;/value&gt;
///    &lt;/data&gt;
/// &lt;/event&gt;
/// </code>
/// </summary>
public sealed class XMLEventFormatter : EventFormatter {
    private static string[] yearStrings                 = Constants.YearStrings;
    private static string[] dateStrings                 = Constants.DateStrings;
    private static string[] millisecondStrings          = Constants.MillisecondStrings;
    
    private const string AMPERSAND                      = "&";
    private const string APOSTROPHE                     = "'";
    private const string BLANK_STRING                   = "";
    private const string COLON                          = ":";
    private const string GREATER_THAN                   = ">";
    private const string HYPHEN                         = "-";
    private const string LESS_THAN                      = "<";
    private const string PERIOD                         = ".";
    private const string QUOTE                          = "\"";
    private const string SPACE                          = " ";
    private const string UNDERSCORE                     = "_";

    private const char AMPERSAND_CHAR                   = '&';
    private const char APOSTROPHE_CHAR                  = '\'';
    private const char COLON_CHAR                       = ':';
    private const char GREATER_THAN_CHAR                = '>';
    private const char HYPHEN_CHAR                      = '-';
    private const char LESS_THAN_CHAR                   = '<';
    private const char PERIOD_CHAR                      = '.';
    private const char QUOTE_CHAR                       = '"';
    private const char SPACE_CHAR                       = ' ';
    private const char UNDERSCORE_CHAR                  = '_';
    
    private static string NEW_LINE                      = new string(new char[] {((char)13), ((char)10)});
    
    private const string VALID_XML_NAME_CHARACTERS      = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private const string TIMESTAMP_FORMAT               = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffzzz";
    
    private const string START_TAG_END                  = "\">";

    private const string SINGLE_TAG_END                 = "\"/>";

    private const char T_CHAR                           = 'T';
    private const char P_CHAR                           = 'P';
    private const char H_CHAR                           = 'H';
    private const char M_CHAR                           = 'M';
    private const char S_CHAR                           = 'S';
    private const string DT                             = "DT";
    
    private const string AMPERSAND_ESCAPE               = "&amp;";
    private const string APOSTROPHE_ESCAPE              = "&apos;";
    private const string LESS_THAN_ESCAPE               = "&lt;";
    private const string GREATER_THAN_ESCAPE            = "&gt;";
    private const string QUOTE_ESCAPE                   = "&quot;";
    
    private const string binaryTypeDescriptor           = "binary";
    private const string booleanTypeDescriptor          = "boolean";
    private const string dateTimeTypeDescriptor         = "dateTime";
    private const string decimalTypeDescriptor          = "decimal";
    private const string doubleTypeDescriptor           = "double";
    private const string timespanTypeDescriptor         = "duration";
    private const string floatTypeDescriptor            = "float";
    private const string integerTypeDescriptor          = "integer";
    private const string stringTypeDescriptor           = "string";
    private const string listTypeDescriptor             = "list";
    private const string dictionaryTypeDescriptor       = "map";
    private const string xmlTypeDescriptor              = "xml";
    
    private string xmlVersionLine                       = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
    
    private string eventListElementName                 = "events";
    private string eventElementName                     = "event";
    private string messageElementName                   = "text";
    private string idAttributeName                      = "id";
    private string timestampAttributeName               = "time";
    private string levelAttributeName                   = "level";
    private string categoryAttributeName                = "category";
    private string codeAttributeName                    = "code";
    private string applicationNameAttributeName         = "application name";
    private string systemNameAttributeName              = "system name";

    private string dataElementName                      = "data";
    private string typeAttributeName                    = "type";
    private string entryElementName                     = "entry";
    private string keyElementName                       = "key";
    private string valueElementName                     = "value";

    private string minimumValue                         = "minimum";
    private string lowValue                             = "low";
    private string normalValue                          = "normal";
    private string highValue                            = "high";
    private string maximumValue                         = "maximum";
    
    private string contextElementName                   = "context";
    private string systemElementName                    = "system";
    private string userElementName                      = "user";
    private string applicationElementName               = "application";
    private string threadElementName                    = "thread";
    private string propertiesElementName                = "properties";
    private string propertyElementName                  = "property";
    private string nameAttributeName                    = "name";
    private string priorityAttributeName                = "priority";
    
    private string contextStartTag;
    private string contextEndTag;
    private string systemStartTag;
    private string userStartTag;
    private string applicationStartTag;
    private string threadStartTag;
    private string propertiesStartTag;
    private string propertiesEndTag;
    private string propertiesSingleTag;
    private string propertyStartTag;
    private string propertyEndTag;
    private string nameAttribute;
    private string priorityAttribute;
    private string propertyTypeAttribute;

    private string eventListStartTag;
    private string eventListEndTag;
    private string eventStartTag;
    private string eventEndTag;
    private string messageStartTag;
    private string messageEndTag;
    private string messageSingleTag;
    //
    private string idAttribute;
    private string timestampAttribute;
    private string levelAttribute;
    private string categoryAttribute;
    private string codeAttribute;
    private string applicationNameAttribute;
    private string systemNameAttribute;
    
    private string dataStartTag;
    private string dataEndTag;
    private string valueStartTag;
    private string valueEndTag;
    private string entryStartTag;
    private string entryEndTag;
    private string keyStartTag;
    private string keyEndTag;
    //
    private string typeAttribute;

    private string indentation                          = "   ";
    private string newLineIndentation                   = NEW_LINE + "   ";
    
    private const int STRING_BUFFER_INITIAL_SIZE        = 128;
    private const int STRING_BUFFER_SIZE_RESET_INTERVAL = 5000;
    private int stringBufferSize                        = STRING_BUFFER_INITIAL_SIZE;
    private int stringBufferCount                       = 0;
    
    private XMLFormatOptions xmlFormatOptions;
    
    /// <summary>
    /// Constructs a new instance using the specified formatting options
    /// </summary>
    /// <param name="_xmlFormatOptions">The formatting options to use in constructing XML</param>
    public XMLEventFormatter(XMLFormatOptions _xmlFormatOptions) {
        xmlFormatOptions = _xmlFormatOptions.Copy();
        dataFormatter = new XMLDataFormatter(xmlFormatOptions);
        Initialize();
    }
    
    /// <summary>
    /// Constructs a new instance using default formatting options
    /// </summary>
    public XMLEventFormatter() {
        xmlFormatOptions = new XMLFormatOptions();
        dataFormatter = new XMLDataFormatter(xmlFormatOptions);
        Initialize();
    }
    
    /// <summary>
    /// Sets the XMLFormatOptions object used by this instance to construct
    /// names of elements and attributes.  This object controls aspects of casing, word
    /// separation in names, and indentation (pretty printing) in generated XML.  The object
    /// passed is cloned before being set on the formatter, so any future changes to the external
    /// XMLFormatOptions object will not be reflected in the output of the formatter unless
    /// it is passed in again
    /// </summary>
    public XMLFormatOptions XMLFormatOptions {
        get {
            return xmlFormatOptions.Copy();
        }
        set {
            if (value == null) {
                throw new ArgumentNullException();
            }
            lock (monitor) {
                xmlFormatOptions = value;
                ((XMLDataFormatter)dataFormatter).XMLFormatOptions = value;
                Initialize();
            }
        }
    }
    
    
    protected override bool UpdateDataFormatter(DataFormatter _newDataFormatter) {
        if (!(_newDataFormatter is XMLDataFormatter)) {
            return false;
        }
        XMLDataFormatter xdf = (XMLDataFormatter)_newDataFormatter;
        xdf.XMLFormatOptions = xmlFormatOptions;
        return true;
    }
    
    protected override bool UpdateEncoding(Encoding _newEncoding) {
        if (_newEncoding == null) {
            return false;
        }
        else if (_newEncoding == Encoding.UTF8) {
            xmlVersionLine = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
        }
        else if (_newEncoding == Encoding.UTF7) {
            xmlVersionLine = "<?xml version=\"1.0\" encoding=\"UTF-7\"?>";
        }
        else if (_newEncoding == Encoding.Unicode) {
            xmlVersionLine = "<?xml version=\"1.0\" encoding=\"UTF-16\"?>";
        }
        else {
            xmlVersionLine = "<?xml version=\"1.0\"?>";
        }
        return true;
    }
    
    public override void Initialize() {
        lock (monitor) {
            XMLFormatOptions xfo      = xmlFormatOptions;

            indentation               = BLANK_STRING.PadLeft(xfo.IndentationWidth);
            newLineIndentation        = NEW_LINE + indentation;
            
            string name               = xfo.GetFormattedElementName(eventListElementName);
            eventListStartTag         = "<" + name + ">";
            eventListEndTag           = "</" + name + ">";            

            name                      = xfo.GetFormattedElementName(eventElementName);
            eventStartTag             = "<" + name;
            eventEndTag               = "</" + name + ">";
            
            name                      = xfo.GetFormattedElementName(messageElementName);
            messageStartTag           = "<" + name + ">";
            messageEndTag             = "</" + name + ">";
            messageSingleTag           = "<" + name + "/>";
            
            
            timestampAttribute        =   " " + xfo.GetFormattedAttributeName(timestampAttributeName)       + "=\"";
            levelAttribute            = "\" " + xfo.GetFormattedAttributeName(levelAttributeName)           + "=\"";
            categoryAttribute         = "\" " + xfo.GetFormattedAttributeName(categoryAttributeName)        + "=\"";
            codeAttribute             = "\" " + xfo.GetFormattedAttributeName(codeAttributeName)            + "=\"";
            applicationNameAttribute  = "\" " + xfo.GetFormattedAttributeName(applicationNameAttributeName) + "=\"";
            systemNameAttribute       = "\" " + xfo.GetFormattedAttributeName(systemNameAttributeName)      + "=\"";
            idAttribute               = "\" " + xfo.GetFormattedAttributeName(idAttributeName)              + "=\"";
            
            name                      = xfo.GetFormattedElementName(dataElementName);
            dataStartTag              = "<" + name + " ";
            dataEndTag                = "</" + name + ">";

            name                      = xfo.GetFormattedElementName(entryElementName);
            entryStartTag             = "<" + name + ">";
            entryEndTag               = "</" + name + ">";
            
            name                      = xfo.GetFormattedElementName(keyElementName);
            keyStartTag               = "<" + name + " ";
            keyEndTag                 = "</" + name + ">";
            
            name                      = xfo.GetFormattedElementName(valueElementName);
            valueStartTag             = "<" + name + " ";
            valueEndTag               = "</" + name + ">";
            
            typeAttribute             = xfo.GetFormattedAttributeName(typeAttributeName)            + "=\"";
            
            name                      = xfo.GetFormattedElementName(contextElementName);
            contextStartTag           = "<" + name + ">";
            contextEndTag             = "</" + name + ">";
            //
            name                      = xfo.GetFormattedElementName(systemElementName);
            systemStartTag            = "<" + name;
            //
            name                      = xfo.GetFormattedElementName(userElementName);
            userStartTag              = "<" + name;
            //
            name                      = xfo.GetFormattedElementName(applicationElementName);
            applicationStartTag       = "<" + name;
            //
            name                      = xfo.GetFormattedElementName(threadElementName);
            threadStartTag            = "<" + name;
            //
            name                      = xfo.GetFormattedElementName(propertiesElementName);
            propertiesStartTag        = "<" + name + ">";
            propertiesEndTag          = "</" + name + ">";
            propertiesSingleTag       = "<" + name + "/>";
            //
            name                      = xfo.GetFormattedElementName(propertyElementName);
            propertyStartTag          = "<" + name;
            propertyEndTag            = "</" + name + ">";
            
            name                      = xfo.GetFormattedAttributeName(nameAttributeName);
            nameAttribute             = " " + name + "=\"";
            
            name                      = xfo.GetFormattedAttributeName(priorityAttributeName);
            priorityAttribute         = " " + name + "=\"";
            
            name                      = xfo.GetFormattedAttributeName(typeAttributeName);
            propertyTypeAttribute     = "\" " + name + "=\"";
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in constructing the start and end tags for lists of
    /// events.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "events"
    /// </summary>
    public string EventListElementName {
        get {
            lock (monitor) {
                return eventListElementName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                eventListElementName        = name;
                name                        = xmlFormatOptions.GetFormattedElementName(eventListElementName);
                eventListStartTag           = "<" + name + ">";
                eventListEndTag             = "</" + name + ">";            
            }
        }
    }

    /// <summary>
    /// Gets and sets the name used in constructing the start and end tags for the XML
    /// representation of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "event"
    /// </summary>
    public string EventElementName {
        get {
            lock (monitor) {
                return eventListElementName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                eventElementName            = name;
                name                        = xmlFormatOptions.GetFormattedElementName(eventElementName);
                eventStartTag               = "<" + name;
                eventEndTag                 = "</" + name + ">";            
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in formatting the Message property of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "text"
    /// </summary>
    public string MessageElementName {
        get {
            lock (monitor) {
                return messageElementName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                messageElementName          = name;
                name                        = xmlFormatOptions.GetFormattedElementName(messageElementName);
                messageStartTag             = "<" + name + ">";
                messageEndTag               = "</" + name + ">";            
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in formatting the Data property of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "data"
    /// </summary>
    public string DataElementName {
        get {
            lock (monitor) {
                return dataElementName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                dataElementName             = name;
                name                        = xmlFormatOptions.GetFormattedElementName(dataElementName);
                dataStartTag                = "<" + name + ">";
                dataEndTag                  = "</" + name + ">";            
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in formatting the Timestamp property of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "time"
    /// </summary>
    public string TimestampAttributeName {
        get {
            lock (monitor) {
                return timestampAttributeName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                timestampAttributeName      = name;
                timestampAttribute          = " " + xmlFormatOptions.GetFormattedAttributeName(timestampAttributeName) + "=\"";
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in formatting the Level property of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "level"
    /// </summary>
    public string LevelAttributeName {
        get {
            lock (monitor) {
                return levelAttributeName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                levelAttributeName          = name;
                levelAttribute              = "\" " + xmlFormatOptions.GetFormattedAttributeName(levelAttributeName) + "=\"";
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in formatting the Category property of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "category"
    /// </summary>
    public string CategoryAttributeName {
        get {
            lock (monitor) {
                return categoryAttributeName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                categoryAttributeName       = name;
                categoryAttribute           = "\" " + xmlFormatOptions.GetFormattedAttributeName(categoryAttributeName) + "=\"";
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in formatting the Code property of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "code"
    /// </summary>
    public string CodeAttributeName {
        get {
            lock (monitor) {
                return codeAttributeName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                codeAttributeName           = name;
                codeAttribute               = "\" " + xmlFormatOptions.GetFormattedAttributeName(codeAttributeName) + "=\"";
            }
        }
    }
    
    /// <summary>
    /// Gets and sets the name used in formatting the ID property of each Event.
    /// The specified string should contain spaces where separators are desired 
    /// in the resulting name; acronyms should be represented in upper case, all 
    /// other characters in lower case.
    /// The default is "id"
    /// </summary>
    public string IDAttributeName {
        get {
            lock (monitor) {
                return idAttributeName;
            }
        }
        set {
            lock (monitor) {
                string name                 = ValidateXMLName(value);
                idAttributeName             = name;
                idAttribute                 = "\" " + xmlFormatOptions.GetFormattedAttributeName(idAttributeName) + "=\"";
            }
        }
    }
    
    /// <summary>
    /// Formats a string as XML, using the XML formatting options set on this instance
    /// </summary>
    /// <param name="_event">The log message to format</param>
    /// <returns>An XML representation of the message</returns>
    public override string Format(Event _event) {
        StringBuilder sb = new StringBuilder(stringBufferSize);
        
        if (xmlFormatOptions.IsVersionLineEnabled) {
            sb.Append(xmlVersionLine);
            if (xmlFormatOptions.IsIndentationEnabled) {
                sb.Append(NEW_LINE);
            }
        }
        
        sb.Append(eventStartTag);
        sb.Append(timestampAttribute);
        sb.Append(yearStrings[_event.Timestamp.Year]).Append(HYPHEN_CHAR);
        sb.Append(dateStrings[_event.Timestamp.Month]).Append(HYPHEN_CHAR);
        sb.Append(dateStrings[_event.Timestamp.Day]).Append(T_CHAR);
        sb.Append(dateStrings[_event.Timestamp.Hour]).Append(COLON_CHAR);
        sb.Append(dateStrings[_event.Timestamp.Minute]).Append(COLON_CHAR);
        sb.Append(dateStrings[_event.Timestamp.Second]).Append(PERIOD_CHAR);
        sb.Append(millisecondStrings[_event.Timestamp.Millisecond]);
        sb.Append(GetLocalTimeZone(_event.Timestamp.Month, _event.Timestamp.Day));
        //
        sb.Append(levelAttribute);
        sb.Append(_event.Level.NameLowerCase);
        //
        if (_event.Category != null)    {   sb.Append(categoryAttribute).Append(Escape(_event.Category));   }
        if (_event.Code != null)        {   sb.Append(codeAttribute).Append(Escape(_event.Code));           }
        if (_event.ID != null)          {   sb.Append(idAttribute).Append(_event.ID);                       }
        //
        sb.Append(START_TAG_END);
        
        
        if (xmlFormatOptions.IsIndentationEnabled) {        
            if (_event.Context != null)     {
                sb.Append(newLineIndentation);
                FormatContext(_event.Context, sb);
            }
            if (_event.Message != null) {   sb.Append(newLineIndentation).Append(messageStartTag).Append(Escape(_event.Message)).Append(messageEndTag);   }
            if (_event.Data != null)    {
                dataFormatter.Format(_event.Data, sb);
            }
            sb.Append(NEW_LINE);
        }
        else {
            if (_event.Context != null)     {   FormatContext(_event.Context, sb);                              }
            if (_event.Message != null) {   sb.Append(messageStartTag).Append(Escape(_event.Message)).Append(messageEndTag);    }
            if (_event.Data != null)    {   dataFormatter.Format(_event.Data, sb);                                              }
        }
        
        sb.Append(eventEndTag);
        
        if (stringBufferCount < STRING_BUFFER_SIZE_RESET_INTERVAL) {
            if (sb.Length > stringBufferSize) {
                stringBufferSize = sb.Length;
            }
            stringBufferCount++;
        }
        else {
            stringBufferSize = STRING_BUFFER_INITIAL_SIZE;
            stringBufferCount = 0;
        }
        
        if (suffix != null) {
            sb.Append(suffix);
        }
        
        return sb.ToString();
    }
    
    private void FormatContext(Context context, StringBuilder sb) {
        string s;
        sb.Append(contextStartTag);
        
        s = context.SystemName;
        if (s != null) {
            if (xmlFormatOptions.IsIndentationEnabled) {
                sb.Append(newLineIndentation).Append(indentation);
            }
            sb.Append(systemStartTag).Append(nameAttribute).Append(Escape(s)).Append(SINGLE_TAG_END);
        }
        
        s = context.UserName;
        if (s != null) {
            if (xmlFormatOptions.IsIndentationEnabled) {
                sb.Append(newLineIndentation).Append(indentation);
            }
            sb.Append(userStartTag).Append(nameAttribute).Append(Escape(s)).Append(SINGLE_TAG_END);
        }
        
        s = context.ApplicationName;
        if (s != null) {
            if (xmlFormatOptions.IsIndentationEnabled) {
                sb.Append(newLineIndentation).Append(indentation);
            }
            sb.Append(applicationStartTag).Append(nameAttribute).Append(Escape(s)).Append(SINGLE_TAG_END);
        }
        
        if (xmlFormatOptions.IsIndentationEnabled) {
            sb.Append(newLineIndentation).Append(indentation);
        }
        sb.Append(threadStartTag);
        s = context.ThreadName;
        if (s != null) {
            sb.Append(nameAttribute).Append(Escape(s)).Append(QUOTE_CHAR).Append(priorityAttribute);
        }
        else {
            sb.Append(priorityAttribute);
        }
        switch (context.ThreadPriority) {
            case ThreadPriority.Lowest      : {
                sb.Append(minimumValue);
                break;
            }
            case ThreadPriority.BelowNormal : {
                sb.Append(lowValue);
                break;
            }
            case ThreadPriority.Normal      : {
                sb.Append(normalValue);
                break;
            }
            case ThreadPriority.AboveNormal : {
                sb.Append(highValue);
                break;
            }
            case ThreadPriority.Highest     : {
                sb.Append(maximumValue);
                break;
            }
            default                         : {
                sb.Append(normalValue);
                break;
            }
        }
        sb.Append(SINGLE_TAG_END);
        
        int propertyCount = context.PropertyCount;
        if (propertyCount == 0) {
            if (xmlFormatOptions.IsIndentationEnabled) {
                sb.Append(newLineIndentation).Append(indentation);
            }
            sb.Append(propertiesSingleTag);
        }
        else {
            if (xmlFormatOptions.IsIndentationEnabled) {
                sb.Append(newLineIndentation).Append(indentation);
                sb.Append(propertiesStartTag);
                for(int x = 0; x < propertyCount; x++) {
                    FormatProperty(context.GetPropertyName(x), context.GetProperty(x), sb);
                }
                sb.Append(newLineIndentation).Append(indentation);
                sb.Append(propertiesEndTag);
            }
            else {
                sb.Append(propertiesStartTag);
                for(int x = 0; x < propertyCount; x++) {
                    FormatProperty(context.GetPropertyName(x), context.GetProperty(x), sb);
                }
                sb.Append(propertiesEndTag);
            }
        }
        
        if (xmlFormatOptions.IsIndentationEnabled) {
            sb.Append(newLineIndentation);
        }
        sb.Append(contextEndTag);
    }

    private void FormatProperty(string propertyName, string propertyValue, StringBuilder sb) {
        if (xmlFormatOptions.IsIndentationEnabled) {
            sb.Append(newLineIndentation).Append(indentation).Append(indentation);
        }
        sb.Append(propertyStartTag).Append(nameAttribute).Append(Escape(propertyName)).Append(propertyTypeAttribute);
        sb.Append(stringTypeDescriptor).Append(START_TAG_END);
        sb.Append(Escape(propertyValue));
        sb.Append(propertyEndTag);
    }
    
    /// <summary>
    /// Provides the header information for a list of formatted events
    /// </summary>
    /// <returns>An opening element tag</returns>
    public override string GetEventListHeader() {
        return eventListStartTag;
    }
    
    /// <summary>
    /// Provides the tail information for a list of formatted events
    /// </summary>
    /// <returns>A closing element tag</returns>
    public override string GetEventListFooter() {
        return eventListEndTag;
    }
    
    private static string Escape(string s) {
        if (s == null) {
            return null;
        }
        if (s.IndexOf(AMPERSAND_CHAR)    >= 0)  {   s = s.Replace(AMPERSAND, AMPERSAND_ESCAPE);         }
        if (s.IndexOf(APOSTROPHE_CHAR)   >= 0)  {   s = s.Replace(APOSTROPHE, APOSTROPHE_ESCAPE);       }
        if (s.IndexOf(LESS_THAN_CHAR)    >= 0)  {   s = s.Replace(LESS_THAN, LESS_THAN_ESCAPE);         }
        if (s.IndexOf(GREATER_THAN_CHAR) >= 0)  {   s = s.Replace(GREATER_THAN, GREATER_THAN_ESCAPE);   }
        if (s.IndexOf(QUOTE_CHAR)        >= 0)  {   s = s.Replace(QUOTE, QUOTE_ESCAPE);                 }
        return s;
    }

    private static string ValidateXMLName(string name) {
        if (name == null) {
            throw new ArgumentNullException();
        }
        name = name.Trim();
        if (name.Equals(BLANK_STRING)) {
            throw new ArgumentException("The name must contain characters");
        }
        name = name.Replace(HYPHEN, SPACE);
        name = name.Replace(UNDERSCORE, SPACE);
        for(int x = 0; x < name.Length; x++) {
            if (VALID_XML_NAME_CHARACTERS.IndexOf(name[x]) < 0) {
                throw new ArgumentException("Character " + name[x] + " not allowed");
            }
        }
        return name;
    }
    
}

}
